﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using ZO.SmartCore.Protocols.XMPP.Properties;
using ZO.SmartCore.Protocols.XMPP.StringPrep;
using ZO.SmartCore.Standards.NamePrep;
using ArgumentNullException= ZO.SmartCore.Core.ArgumentNullException;

#endregion

namespace ZO.SmartCore.Protocols.XMPP.Core
{
    /// <summary>
    /// Object representation of a Jabber Identifier (JID). 
    /// </summary>
    /// <remarks>
    /// The JID uses "user" to refer to the "username" portion, 
    /// "domain" to refer to the "hostname" portion, and 
    /// "resource" for the JID's resource. <br />
    /// The JID allows "empty" values for user and resource.
    /// </remarks>
    public sealed class JID : IComparable<JID>, IEquatable<JID>
    {

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="JID"/> class with the given string.
        /// </summary>
        /// <param name="fullJID">The fully-qualified string to parse, in string form examples: user@server/Resource, user@server </param>
        /// <exception cref="JIDFormatException">throws JIDFormatException if domain, user, or resource do not meet the 'stringprep' resources outlined in the XMPP specifications</exception>
        public JID(string fullJID)
        {
            Match m = JID_Regex.Match(!String.IsNullOrEmpty(fullJID) ? fullJID : "");

            if (!m.Success)
            {
                throw new JIDFormatException(String.Format(CultureInfo.InvariantCulture, Resources.Bad_JID, new string[] { !String.IsNullOrEmpty(fullJID) ? fullJID : "" }));
            } // if

            this.Domain = m.Groups[POS_DOMAIN].ToString();
            this.Resource = m.Groups[POS_RESOURCE].ToString();
            this.User = m.Groups[POS_USER].ToString();

            this._JID = Build(this.User, this.Domain, this.Resource);

        } // JID    



        /// <summary>
        /// Initializes a new instance of the <see cref="JID"/> class with the given parameters.
        /// </summary>
        /// <param name="user">The user for this JID.</param>
        /// <param name="domain">The server for this JID.</param>
        /// <param name="resource">The resource for this JID.</param>
        public JID(string user, string domain, string resource)
        {
            if (String.IsNullOrEmpty(user))
            {
                throw new ArgumentNullException("user");
            }

            if (String.IsNullOrEmpty(domain))
            {
                throw new ArgumentNullException("domain");
            }

            if (String.IsNullOrEmpty(resource))
            {
                throw new ArgumentNullException("resource");
            }


            this.Domain = domain;
            this.Resource = resource;
            this.User = user;

            this._JID = Build(this.User, this.Domain, this.Resource);

        } // JID
        #endregion

        #region Destructor

        #endregion

        #region Fields
        private string _JID;
        private string _User;
        private string _Domain;
        private string _Resource;

        /// <summary>
        /// JID Regular Expression
        /// </summary>
        private readonly static Regex JID_Regex = new Regex("(([^@]+)@)?([^/]+)(/(.*))?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
        private const int POS_USER = 2;
        private const int POS_DOMAIN = 3;
        private const int POS_RESOURCE = 5;

        #endregion

        #region Events

        #endregion

        #region Operators
        /// <summary>
        /// Implicit operators the specified fullJID.
        /// </summary>
        /// <param name="other">The fullJID.</param>
        /// <returns></returns>
        public static implicit operator string(JID other)
        {
            if (other == null)
            {
                return null;
            }

            return other._JID;
        } // string


        /// <summary>
        /// Implicit operators the specified fullJID.
        /// </summary>
        /// <param name="fullJID">The fullJID.</param>
        /// <returns></returns>
        public static implicit operator JID(string fullJID)
        {
            return new JID(fullJID);
        } // JID

        /// <summary>
        /// less than
        /// </summary>
        public static bool operator <(JID left, JID right)
        {
            if (left == null)
            {
                throw new ArgumentNullException("left");
            }

            return left.CompareTo(right) == -1;
        }

        /// <summary>
        /// greater than
        /// </summary>
        public static bool operator >(JID left, JID right)
        {
            return (right < left);
        }

        /// <summary>
        /// less than or equal
        /// </summary>
        public static bool operator <=(JID left, JID right)
        {
            if (left == null)
            {
                throw new ArgumentNullException("left");
            }
            return left.CompareTo(right) != -1;
        }

        /// <summary>
        /// greater than or equal
        /// </summary>
        public static bool operator >=(JID left, JID right)
        {
            return (right <= left);
        }

        /// <summary>
        /// Two jids are equal
        /// </summary>
        public static bool operator ==(JID left, JID right)
        {
            if ((object)left == null)
                return ((object)right == null);
            return left.Equals(right);
        }

        /// <summary>
        /// Is this string equal to that fullJID?
        /// </summary>
        public static bool operator ==(string left, JID right)
        {
            if ((object)right == null)
                return (String.IsNullOrEmpty(left));
            return right.Equals(left);
        }

        /// <summary>
        /// Is this string not equal to that fullJID?
        /// </summary>
        public static bool operator !=(string left, JID right)
        {
            if ((object)right == null)
                return (String.IsNullOrEmpty(left));
            return !right.Equals(left);
        }

        /// <summary>
        /// Two jids are unequal?
        /// </summary>
        /// <param name="one"></param>
        /// <param name="two"></param>
        /// <returns></returns>
        public static bool operator !=(JID one, JID two)
        {
            if ((object)one == null)
                return ((object)two != null);
            return !one.Equals(two);
        }
        #endregion

        #region Properties
        /// <summary>
        /// Get the Domain to which the user connect
        /// </summary>
        public string Domain
        {
            get
            {
                return this._Domain;
            }
            internal set
            {
                try
                {
                    this._Domain = NamePrepProfile.GetInstance().Prepare(value);
                } // try
                catch (Exception e)
                {
                    throw new JIDFormatException(e.Message, e);
                } // catch
            } // set
        }

        /// <summary>
        /// Get the Bare Jid only (user@server). 
        /// </summary>
        public string Bare
        {
            get
            {
                return Build(this.User, this.Domain, null);
            }
        }

        /// <summary>
        /// Get The User name
        /// </summary>
        public string User
        {
            get
            {
                return this._User;
            }
            internal set
            {
                try
                {
                    this._User = (String.IsNullOrEmpty(value) ? "" : NodePrepProfile.GetInstance().Prepare(value));
                } // try
                catch (Exception e)
                {
                    throw new JIDFormatException(e.Message, e);
                } // catch
            } // set
        }

        /// <summary>
        /// Get the the user connected resource (e.g., a specific client) in the form of user@host/resource
        /// </summary>
        public string Resource
        {
            get
            {
                return this._Resource;
            }
            internal set
            {
                try
                {
                    this._Resource = (String.IsNullOrEmpty(value) ? "" : ResourcePrepProfile.GetInstance().Prepare(value));
                } // try
                catch (Exception e)
                {
                    throw new JIDFormatException(e.Message, e);
                } // catch
            } // set

        }

        #endregion

        #region Methods
        private static string Build(string user, string server, string resource)
        {
            StringBuilder sb = new StringBuilder();
            if (!String.IsNullOrEmpty(user))
            {
                sb.Append(user);
                sb.Append("@");
            }
            sb.Append(server);
            if (!String.IsNullOrEmpty(resource))
            {
                sb.Append("/");
                sb.Append(resource);
            }
            return sb.ToString();
        }


        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return this._JID;
        }

        /// <summary>
        /// Equalses the specified obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            string tempOther = obj as string;

            if (!String.IsNullOrEmpty(tempOther))
            {
                return this._JID.Equals(tempOther);
            }

            return this.Equals(obj as JID);

        }

        /// <summary>
        /// Serves as a hash function for a particular type. <see cref="M:System.Object.GetHashCode"></see> is suitable for use in hashing algorithms and data structures like a hash table.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override int GetHashCode()
        {
            return this._JID.GetHashCode();
        }

        /// <summary>
        /// Determines whether the specified name is JID.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>
        /// 	<c>true</c> if the specified name is JID; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsJID(string name)
        {
            Match m = JID_Regex.Match(!String.IsNullOrEmpty(name) ? name : "");

            if (m.Success)
            {
                return true;
            } // if

            return false;

        }
        #endregion

        #region IEquatable<JID> Members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the other parameter; otherwise, false.
        /// </returns>
        public bool Equals(JID other)
        {
            if (other != null)
            {
                return this._JID.Equals(other._JID);
            }

            return false;
        }

        #endregion

        #region IComparable<JID> Members

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the obj parameter.Zero This object is equal to obj. Greater than zero This object is greater than obj.
        /// </returns>
        public int CompareTo(JID other)
        {
            if (other == null)
            {
                return -1;
            }

            return this._JID.CompareTo(other._JID);
        }


        #endregion
    }
}
